package core;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.LineNumberReader;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;

import spatialindex.rtree.RTree;
import spatialindex.spatialindex.IData;
import spatialindex.spatialindex.IEntry;
import spatialindex.spatialindex.INode;
import spatialindex.spatialindex.IQueryStrategy;
import spatialindex.spatialindex.IShape;
import spatialindex.spatialindex.ISpatialIndex;
import spatialindex.spatialindex.IVisitor;
import spatialindex.spatialindex.Point;
import spatialindex.spatialindex.Region;
import spatialindex.storagemanager.DiskStorageManager;
import spatialindex.storagemanager.IBuffer;
import spatialindex.storagemanager.IStorageManager;
import spatialindex.storagemanager.PropertySet;
import spatialindex.storagemanager.RandomEvictionsBuffer;
import util.Constants;
import NET.sourceforge.BplusJ.BplusJ.BplusTree;

/**
 * Index the road network<br>
 * 
 * @author Xiaohui
 * @version 0.1 include jid's in the data in leaf nodes
 */
public class RoadNetworkIndex {

	/**
	 * File Format: eid,x1,y1,x2,y2 network/edgedata.csv
	 * 
	 * @param edgeGeomFile
	 * @throws Exception
	 */
	public void indexRoadNetworkByEdgeId() throws Exception {
		BplusTree btree = BplusTree.Initialize(Constants.networkBTreeFileByEid,
				Constants.networkBlockFileByEid, Constants.sampleEid.length());
		BufferedReader in;
		try {
			in = new BufferedReader(new FileReader(Constants.edgeGeomFile));
			String str;
			while ((str = in.readLine()) != null) {
				int i = str.indexOf(",");
				btree.Set(str.substring(0, i), str.substring(i + 1));
			}
			btree.Commit();
			in.close();
			System.out.println("indexRoadNetworkByEdgeId Done!");
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Primary geometry object is edge
	 * 
	 * @param args
	 */
	void RTreeLoadEdge(String rawFile, String treeFile, int capacity,
			String queryType) {
		int dimension = 2;
		String delimiter = ",";
		System.out.println("Indexing road network...");
		try {
			LineNumberReader lr = null;

			try {
				lr = new LineNumberReader(new FileReader(rawFile));
			} catch (FileNotFoundException e) {
				System.err.println("Cannot open data file " + rawFile + ".");
				System.exit(-1);
			}

			// Create a disk based storage manager.
			PropertySet ps = new PropertySet();

			Boolean b = new Boolean(true);
			ps.setProperty("Overwrite", b);
			// overwrite the file if it exists.

			ps.setProperty("FileName", treeFile);
			// .idx and .dat extensions will be added.

			Integer i = new Integer(4096);
			ps.setProperty("PageSize", i);
			// specify the page size. Since the index may also contain user
			// defined data
			// there is no way to know how big a single node may become. The
			// storage manager
			// will use multiple pages per node if needed. Off course this will
			// slow down performance.

			IStorageManager diskfile = new DiskStorageManager(ps);

			IBuffer file = new RandomEvictionsBuffer(diskfile, 10, false);
			// applies a main memory random buffer on top of the persistent
			// storage manager
			// (LRU buffer, etc can be created the same way).

			// Create a new, empty, RTree with dimensionality 2, minimum load
			// 70%, using "file" as
			// the StorageManager and the RSTAR splitting policy.
			PropertySet ps2 = new PropertySet();

			Double f = new Double(0.7);
			ps2.setProperty("FillFactor", f);

			ps2.setProperty("IndexCapacity", capacity);
			ps2.setProperty("LeafCapacity", capacity);
			// Index capacity and leaf capacity may be different.

			ps2.setProperty("Dimension", dimension);

			ISpatialIndex tree = new RTree(ps2, file);

			int count = 0;
			int indexIO = 0;
			int leafIO = 0;
			int id; // edge id is long type, so created another id to
			// rep each edge
			long edgeid;
			int x1, x2, y1, y2, jid1, jid2; // nodes id's are all integers
			double[] f1 = new double[2];
			double[] f2 = new double[2];

			String line = lr.readLine();

			while (line != null) {
				// lines starts with # are comments
				if (!line.startsWith("#")) {
					StringTokenizer st = new StringTokenizer(line, delimiter);
					edgeid = new Long(st.nextToken());
					jid1 = new Integer(st.nextToken());
					jid2 = new Integer(st.nextToken());
					x1 = new Integer(st.nextToken());
					y1 = new Integer(st.nextToken());
					x2 = new Integer(st.nextToken());
					y2 = new Integer(st.nextToken());

					// insert
					f1[0] = x1;
					f1[1] = y1;
					f2[0] = x2;
					f2[1] = y2;
					Region r = new Region(f1, f2);
					// data format: eid jid1 jid2 x1 y1 x2 y2
					String data = edgeid + " " + jid1 + " " + jid2 + " " + x1
							+ " " + y1 + " " + x2 + " " + y2;

					tree.insertData(data.getBytes(), r, count);
					if ((count % 1000) == 0)
						System.err.println(count);
					count++;
				}
				line = lr.readLine();
			}
			/**
			 * System.err.println("Operations: " + count);
			 * System.err.println(tree); System.err.println("Minutes: " + ((end
			 * - start) / 1000.0f) / 60.0f);
			 * 
			 * // since we created a new RTree, the PropertySet that was used to
			 * // initialize the structure // now contains the IndexIdentifier
			 * property, which can be used // later to reuse the index. //
			 * (Remember that multiple indices may reside in the same storage //
			 * manager at the same time // and every one is accessed using its
			 * unique IndexIdentifier). Integer indexID = (Integer)
			 * ps2.getProperty("IndexIdentifier");
			 * System.err.println("Index ID: " + indexID);
			 **/
			boolean ret = tree.isIndexValid();
			if (ret == false)
				System.err.println("Structure is INVALID!");

			// flush all pending changes to persistent storage (needed since
			// Java might not call finalize when JVM exits).
			tree.flush();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public Set<Integer> RTreeQuery4Jids(QueryParser qparser,
			String netWorkTreeFile, String queryType) throws Exception {
		// Create a disk based storage manager.
		PropertySet ps = new PropertySet();

		ps.setProperty("FileName", netWorkTreeFile);
		// .idx and .dat extensions will be added.

		IStorageManager diskfile = new DiskStorageManager(ps);

		IBuffer file = new RandomEvictionsBuffer(diskfile, 10, false);
		// applies a main memory random buffer on top of the persistent storage
		// manager
		// (LRU buffer, etc can be created the same way).

		PropertySet ps2 = new PropertySet();

		// If we need to open an existing tree stored in the storage manager, we
		// only
		// have to specify the index identifier as follows
		Integer i = new Integer(1); // INDEX_IDENTIFIER_GOES_HERE (suppose I
		// know that in this case it is equal to 1);
		ps2.setProperty("IndexIdentifier", i);
		// this will try to locate and open an already existing r-tree index
		// from file manager file.

		ISpatialIndex tree = new RTree(ps2, file);

		int indexIO = 0;
		int leafIO = 0;
		int op;
		double[] f1 = new double[2];
		double[] f2 = new double[2];
		JidVisitor vis = new JidVisitor();
		op = qparser.getOp();
		f1[0] = qparser.getQx1();
		f1[1] = qparser.getQy1();
		f2[0] = qparser.getQx2();
		f2[1] = qparser.getQy2();

		if (queryType.equals("intersection")) {
			Region r = new Region(f1, f2);
			tree.intersectionQuery(r, vis);
			// this will find all data that intersect with the query
			// range.
		} else if (queryType.equals("10NN")) {
			Point p = new Point(f1);
			tree.nearestNeighborQuery(10, p, vis);
			// this will find the 10 nearest neighbors.
		} else {
			System.err.println("Unknown query type.");
			System.exit(-1);
		}

		indexIO += vis.m_indexIO;
		leafIO += vis.m_leafIO;
		// example of the Visitor pattern usage, for calculating how
		// many nodes
		// were visited.
		MyQueryStrategy2 qs = new MyQueryStrategy2();
		tree.queryStrategy(qs);
		tree.flush();
		return vis.getJidSet();
	}

	public List<String> RTreeQuery4Data(QueryParser qparser,
			String netWorkTreeFile, String queryType) throws Exception {
		// Create a disk based storage manager.
		PropertySet ps = new PropertySet();

		ps.setProperty("FileName", netWorkTreeFile);
		// .idx and .dat extensions will be added.

		IStorageManager diskfile = new DiskStorageManager(ps);

		IBuffer file = new RandomEvictionsBuffer(diskfile, 10, false);
		// applies a main memory random buffer on top of the persistent storage
		// manager
		// (LRU buffer, etc can be created the same way).

		PropertySet ps2 = new PropertySet();

		// If we need to open an existing tree stored in the storage manager, we
		// only
		// have to specify the index identifier as follows
		Integer i = new Integer(1); // INDEX_IDENTIFIER_GOES_HERE (suppose I
		// know that in this case it is equal to 1);
		ps2.setProperty("IndexIdentifier", i);
		// this will try to locate and open an already existing r-tree index
		// from file manager file.

		ISpatialIndex tree = new RTree(ps2, file);

		int indexIO = 0;
		int leafIO = 0;
		int op;
		double[] f1 = new double[2];
		double[] f2 = new double[2];
		DataVisitor vis = new DataVisitor();
		op = qparser.getOp();
		f1[0] = qparser.getQx1();
		f1[1] = qparser.getQy1();
		f2[0] = qparser.getQx2();
		f2[1] = qparser.getQy2();

		if (queryType.equals("intersection")) {
			Region r = new Region(f1, f2);
			tree.intersectionQuery(r, vis);
			// this will find all data that intersect with the query
			// range.
		} else if (queryType.equals("10NN")) {
			Point p = new Point(f1);
			tree.nearestNeighborQuery(10, p, vis);
			// this will find the 10 nearest neighbors.
		} else {
			System.err.println("Unknown query type.");
			System.exit(-1);
		}

		indexIO += vis.m_indexIO;
		leafIO += vis.m_leafIO;
		// example of the Visitor pattern usage, for calculating how
		// many nodes
		// were visited.
		MyQueryStrategy2 qs = new MyQueryStrategy2();
		tree.queryStrategy(qs);
		tree.flush();
		return vis.getEdgeDataList();
	}

	/**
	 * use data visitor to retrieve edge geometry <br>
	 * called by tims Edge data format in r tree's leaf nodes: eid x1 y1 : x2 y2
	 * 
	 * @param queryFile
	 * @param netWorkTreeFile
	 * @param queryType
	 * @return leaf data as a list of strings
	 * @throws Exception
	 */
	public List<String> RTreeQuery4Data(String queryFile,
			String netWorkTreeFile, String queryType) throws Exception {
		LineNumberReader lr = null;
		try {
			lr = new LineNumberReader(new FileReader(queryFile));
		} catch (FileNotFoundException e) {
			System.err.println("Cannot open query file " + queryFile + ".");
			System.exit(-1);
		}
		// Create a disk based storage manager.
		PropertySet ps = new PropertySet();

		ps.setProperty("FileName", netWorkTreeFile);
		// .idx and .dat extensions will be added.

		IStorageManager diskfile = new DiskStorageManager(ps);

		IBuffer file = new RandomEvictionsBuffer(diskfile, 10, false);
		// applies a main memory random buffer on top of the persistent storage
		// manager
		// (LRU buffer, etc can be created the same way).

		PropertySet ps2 = new PropertySet();

		// If we need to open an existing tree stored in the storage manager, we
		// only
		// have to specify the index identifier as follows
		Integer i = new Integer(1); // INDEX_IDENTIFIER_GOES_HERE (suppose I
		// know that in this case it is equal to 1);
		ps2.setProperty("IndexIdentifier", i);
		// this will try to locate and open an already existing r-tree index
		// from file manager file.

		ISpatialIndex tree = new RTree(ps2, file);

		int indexIO = 0;
		int leafIO = 0;
		int id, op;
		double x1, x2, y1, y2;
		double[] f1 = new double[2];
		double[] f2 = new double[2];

		String line = lr.readLine();
		DataVisitor vis = new DataVisitor();
		while (line != null) {

			StringTokenizer st = new StringTokenizer(line);
			op = new Integer(st.nextToken()).intValue();
			id = new Integer(st.nextToken()).intValue();
			x1 = new Double(st.nextToken()).doubleValue();
			y1 = new Double(st.nextToken()).doubleValue();
			x2 = new Double(st.nextToken()).doubleValue();
			y2 = new Double(st.nextToken()).doubleValue();

			if (op == 2) {
				// query
				f1[0] = x1;
				f1[1] = y1;
				f2[0] = x2;
				f2[1] = y2;

				if (queryType.equals("intersection")) {
					Region r = new Region(f1, f2);
					tree.intersectionQuery(r, vis);
					// this will find all data that intersect with the query
					// range.
				} else if (queryType.equals("10NN")) {
					Point p = new Point(f1);
					tree.nearestNeighborQuery(10, p, vis);
					// this will find the 10 nearest neighbors.
				} else {
					System.err.println("Unknown query type.");
					System.exit(-1);
				}

				indexIO += vis.m_indexIO;
				leafIO += vis.m_leafIO;
				// example of the Visitor pattern usage, for calculating how
				// many nodes
				// were visited.
			} else {
				System.err.println("This is not a query operation.");
			}

			line = lr.readLine();
		}

		MyQueryStrategy2 qs = new MyQueryStrategy2();
		tree.queryStrategy(qs);
		/**
		 * System.err.println("Indexed space: " + qs.m_indexedSpace);
		 * System.err.println("Operations: " + count); System.err.println(tree);
		 * System.err.println("Index I/O: " + indexIO);
		 * System.err.println("Leaf I/O: " + leafIO);
		 * System.err.println("Minutes: " + ((end - start) / 1000.0f) / 60.0f);
		 **/
		// flush all pending changes to persistent storage (needed since Java
		// might not call finalize when JVM exits).
		tree.flush();
		return vis.getEdgeDataList();
	}

	/**
	 * 
	 * @param query_file
	 * @param treeFile
	 * @param queryType
	 * @return
	 * @throws Exception
	 */
	public Set<Long> RTreeQuery4Rid(QueryParser qparser, String treeFile,
			String queryType) throws Exception {
		// Create a disk based storage manager.
		PropertySet ps = new PropertySet();
		ps.setProperty("FileName", treeFile);
		// .idx and .dat extensions will be added.

		IStorageManager diskfile = new DiskStorageManager(ps);

		IBuffer file = new RandomEvictionsBuffer(diskfile, 10, false);
		// applies a main memory random buffer on top of the persistent storage
		// manager
		// (LRU buffer, etc can be created the same way).

		PropertySet ps2 = new PropertySet();

		// If we need to open an existing tree stored in the storage manager, we
		// only
		// have to specify the index identifier as follows
		Integer i = new Integer(1); // INDEX_IDENTIFIER_GOES_HERE (suppose I
		// know that in this case it is equal to 1);
		ps2.setProperty("IndexIdentifier", i);
		// this will try to locate and open an already existing r-tree index
		// from file manager file.

		ISpatialIndex tree = new RTree(ps2, file);

		int indexIO = 0;
		int leafIO = 0;
		int op;
		double[] f1 = new double[2];
		double[] f2 = new double[2];

		RidVisitor vis = new RidVisitor();
		op = qparser.getOp();

		if (op == 2) {
			// query
			f1[0] = qparser.getQx1();
			f1[1] = qparser.getQy1();
			f2[0] = qparser.getQx2();
			f2[1] = qparser.getQy2();

			if (queryType.equals("intersection")) {
				Region r = new Region(f1, f2);
				tree.intersectionQuery(r, vis);
				// this will find all data that intersect with the query
				// range.
			} else if (queryType.equals("10NN")) {
				Point p = new Point(f1);
				tree.nearestNeighborQuery(10, p, vis);
				// this will find the 10 nearest neighbors.
			} else {
				System.err.println("Unknown query type.");
				System.exit(-1);
			}

			indexIO += vis.m_indexIO;
			leafIO += vis.m_leafIO;
			// example of the Visitor pattern usage, for calculating how
			// many nodes
			// were visited.
		} else {
			System.err.println("This is not a query operation.");
		}

		MyQueryStrategy2 qs = new MyQueryStrategy2();
		tree.queryStrategy(qs);

		// flush all pending changes to persistent storage (needed since Java
		// might not call finalize when JVM exits).
		tree.flush();
		return vis.getRids();
	}

	class DataVisitor implements IVisitor {
		public int m_indexIO = 0;
		public int m_leafIO = 0;
		List<String> edgeDataList = new LinkedList<String>();

		public void visitNode(final INode n) {
			if (n.isLeaf())
				m_leafIO++;
			else
				m_indexIO++;
		}

		public void visitData(final IData d) {
			String data = new String(d.getData());
			// System.out.println(data);
			edgeDataList.add(data);
		}

		public List<String> getEdgeDataList() {
			return edgeDataList;

		}
	}

	class JidVisitor implements IVisitor {
		public int m_indexIO = 0;
		public int m_leafIO = 0;
		int i1, i2, i3;
		Set<Integer> intersectedJids = new HashSet<Integer>();

		public void visitNode(final INode n) {
			if (n.isLeaf())
				m_leafIO++;
			else
				m_indexIO++;
		}

		public void visitData(final IData d) {
			String data = new String(d.getData());
			// System.out.println(data);
			i1 = data.indexOf(" ");
			i2 = data.indexOf(" ", i1 + 1);
			i3 = data.indexOf(" ", i2 + 1);
			intersectedJids.add(Integer.parseInt(data.substring(i1 + 1, i2)));
			intersectedJids.add(Integer.parseInt(data.substring(i2 + 1, i3)));
		}

		Set<Integer> getJidSet() {
			return intersectedJids;
		}
	}

	// example of a Visitor pattern.
	// see RTreeQuery for a more elaborate example.
	class RidVisitor implements IVisitor {
		public int m_indexIO = 0;
		public int m_leafIO = 0;
		Set<Long> rids = new HashSet<Long>();

		public void visitNode(final INode n) {
			if (n.isLeaf())
				m_leafIO++;
			else
				m_indexIO++;
		}

		public void visitData(final IData d) {
			String data = new String(d.getData());
			// System.out.println(data);
			long arid = Long.parseLong(data.substring(0, data.indexOf(" ")));
			rids.add(arid);
			// // the ID of this data entry is an answer to the query. I will
			// just
			// print it to stdout.
		}

		public Set<Long> getRids() {
			return rids;
		}
	}

	// example of a Strategy pattern.
	// find the total indexed space managed by the index (the MBR of the root).
	class MyQueryStrategy2 implements IQueryStrategy {
		public Region m_indexedSpace;

		public void getNextEntry(IEntry entry, int[] nextEntry,
				boolean[] hasNext) {
			// the first time we are called, entry points to the root.
			IShape s = entry.getShape();
			m_indexedSpace = s.getMBR();

			// stop after the root.
			hasNext[0] = false;
		}
	}

}
